#include "utils.h"
#include "hash.h"
#include <string.h>
#include <stdlib.h>

/*
 * Effectue la correspondance entre maccro et type LLVM
 */
char *to_string(int type){
    switch (type) { 
        case U :
            return "undefined";
            break;
        case I :
            return "i32";
            break;
        case F :
            return "double";
            break;
        case S :
            return "string";
            break;
        case C :
            return "class";
            break;
        case B :
            return "i1";
            break;
        case Fun :
            return "fonction";
            break;
        default:
            return "undefined";
            break;
    }
}

/*
 * Remplace la chaine rep par with dans orig
 */
char *replace_str(char *orig, char *rep, char *with) {
    char *result; // Chaine retournée
    char *ins;    // Prochain fragment à modifier
    char *tmp;    
    int len_rep;  // Longueur de rep
    int len_with; // Longueur de with
    int len_front;// Distance entre rep et la fin de rep
    int count;    // Nombre de replacements

    if (!orig)
        return NULL;
    if (!rep || !(len_rep = strlen(rep)))
        return NULL;
    if (!(ins = strstr(orig, rep))) 
        return NULL;
    if (!with)
        with = "";
    len_with = strlen(with);

    for (count = 0; (tmp = strstr(ins, rep)); ++count) {
        ins = tmp + len_rep;
    }

    tmp = result = malloc(strlen(orig) + (len_with - len_rep) * count + 1);

    if (!result)
        return NULL;

    while (count--) {
        ins = strstr(orig, rep);
        len_front = ins - orig;
        tmp = strncpy(tmp, orig, len_front) + len_front;
        tmp = strcpy(tmp, with) + len_with;
        orig += len_front + len_rep;
    }
    strcpy(tmp, orig);
    return result;
}

/*
 * Retire le premier caractère de la chaine passée en prametre
 */
void pop_char(char *s){
    int i = 0;
    for(i = 0; i < (int)strlen(s); i++){
        s[i] = s[i+1];
    }
}

/*
 * Copie la structure r1 dans la structure r2
 */
void copy(struct r *r1, struct r *r2){
    strcpy(r1->inst, r2->inst);//On copie l'instruction au cas où (même si il n'y en a pas...)
    strcpy(r1->reg, r2->reg);//On récupère le registre ou la variable ou le nombre
    strcpy(r1->id, r2->id);
    r1->type = r2->type;
}

/*
 * Effectue la traduction fonction rubic/llvm
 */
void print_fct(char *dest, char *id, char *params, char *inst, char *type){
    char tmp[1000];
    if(strcmp(type,"undefined") ==  0){
        sprintf(tmp,"define void @%s(",id);
    }
    else{
        sprintf(tmp,"define %s* @%s(",type,id);
    }
    strcat(dest,tmp);
    char* tok = strtok(params,",");
    sprintf(tmp,"%s* %%%s",to_string(hash_type[hash(tok)]), tok);
    strcat(dest,tmp);
    tok = strtok(NULL,",");
    while(tok != NULL){
        sprintf(tmp,",%s* %%%s",to_string(hash_type[hash(tok)]),tok);
        strcat(dest,tmp);
        tok = strtok(NULL,",");
    }
    strcat(dest,") {\n");
    sprintf(tmp,"%s",inst);
    strcat(dest,tmp);
    if(strcmp(type,"undefined") ==  0){
        strcat(dest,"ret void");
    }
    strcat(dest,"}\n\n");
}

/* 
 * Ecrit le resultat de l'opération: res = x oper1 oper2 y
 * ou oper2 peut être une chaine vide.
 */
void print_calc(struct r *res, struct r *x, struct r *y, char* oper1, char* oper2, int *indice){
    char tmp[1000];
    char op[1000];
    strcpy(res->inst,x->inst);
    strcat(res->inst,y->inst);
    sprintf(tmp,"%%%d = load %s* %s\n",*indice,to_string(x->type),x->reg);
    strcat(res->inst,tmp);
    (*indice)++;
    sprintf(tmp,"%%%d = load %s* %s\n",*indice,to_string(y->type),y->reg);
    strcat(res->inst,tmp);
    (*indice)++;
    if((strcmp(oper2,"eq") == 0 || strcmp(oper2,"ne") == 0) && (x->type == F || y->type == F)){
        strcpy(op,"o");
        strcat(op,oper2);
    }
    else if(strcmp(oper2,"") != 0 && (x->type == F || y->type == F)){
        strcpy(op,oper2);
        op[0] = 'o';
    }
    if(strcmp(oper1,"and") != 0 && strcmp(oper1,"or") != 0 && (x->type == F || y->type == F)){
        if(x->type == F && y->type != F){
            sprintf(tmp,"%%%d = uitofp %s %%%d to double\n",*indice,to_string(y->type),(*indice)-1);
            strcat(res->inst,tmp);
            (*indice)++;
            sprintf(tmp,"%%%d = f%s %s double %%%d, %%%d\n",*indice,oper1, op, (*indice)-3,(*indice)-1);
            strcat(res->inst,tmp);
            (*indice)++;
            res->type = F;
        }
        else if(x->type != F && y->type == F){
            sprintf(tmp,"%%%d = uitofp %s %%%d to double\n",*indice,to_string(x->type),(*indice)-2);
            strcat(res->inst,tmp);
            (*indice)++;
            sprintf(tmp,"%%%d = f%s %s double %%%d, %%%d\n",*indice,oper1, op, (*indice)-2,(*indice)-1);
            strcat(res->inst,tmp);
            (*indice)++;
            res->type = F;
        }
        else if(x->type == F && y->type == F){
            sprintf(tmp,"%%%d = f%s %s double %%%d, %%%d\n",*indice,oper1, op, (*indice)-2,(*indice)-1);
            strcat(res->inst,tmp);
            (*indice)++;
            res->type = F;
        }
    }
    else{
        if(strcmp(oper1,"cmp") == 0)
            sprintf(tmp,"%%%d = i%s %s %s %%%d, %%%d\n",*indice,oper1, oper2, to_string(x->type),(*indice)-2,(*indice)-1);
        else if(strcmp(oper1,"div") == 0)
            sprintf(tmp,"%%%d = s%s %s %s %%%d, %%%d\n",*indice,oper1, oper2, to_string(x->type),(*indice)-2,(*indice)-1);
        else
            sprintf(tmp,"%%%d = %s %s %s %%%d, %%%d\n",*indice,oper1, oper2, to_string(x->type),(*indice)-2,(*indice)-1);
        strcat(res->inst,tmp);
        (*indice)++;
        res->type = x->type;
    }
    if(strcmp(oper1,"cmp") == 0 || strcmp(oper1,"and") == 0 || strcmp(oper1,"or") == 0){
        res->type = B;
    }
    sprintf(tmp,"%%%d = alloca %s\n",*indice,to_string(res->type));
    strcat(res->inst,tmp);
    sprintf(tmp,"store %s %%%d, %s* %%%d\n",to_string(res->type),(*indice)-1,to_string(res->type),*indice);//On effectue l'affectation
    strcat(res->inst,tmp);
    sprintf(res->reg,"%%%d",*indice);
    (*indice)++;
    strcpy(op,"");
}

/*
 * Permet d'initialiser un int ou un float
 */
void print_init(struct r *res, char *prim, char *type, int *indice){
    char tmp[1000];
    sprintf(tmp,"%%%d = alloca %s\n",*indice,type);
    strcat(res->inst,tmp);
    sprintf(tmp,"store %s %s, %s* %%%d\n",type,prim,type,*indice);//On effectue l'affectation
    strcat(res->inst,tmp);
    sprintf(res->reg,"%%%d",*indice);//Le registre vaut 1 ou 2 ou 3 ....
    strcpy(res->id,prim);
    (*indice)++;
    if(strcmp("i32",type) == 0)
        res->type = I;
    else if(strcmp("double",type) == 0)
        res->type = F;
}

void print_begin(FILE * file) {
        fprintf(file,"@str = constant [ 7 x i8 ] c\"=> %%f\\0A\\00\"\n\n");
        fprintf(file,"declare i32 @printf(i8*, ...)\n\n");
        fprintf(file,"define double* @calcule() {\n");
}

void print_end(FILE * file) {
        fprintf(file,"\n}\n\ndefine i32 @main() {\n");
        fprintf(file,"\t%%x = call double* @calcule()\n");
        fprintf(file,"\t%%y = load double* %%x\n");
        fprintf(file,"\tcall i32 (i8*, ...)* @printf(i8* getelementptr ([7 x i8]* @str, i32 0, i32 0), double %%y)\n");
        fprintf(file,"\tret i32 0\n}");
}